package com.wmada.toolkit.common;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.apache.commons.lang3.StringUtils;
import org.nustaq.serialization.FSTObjectInput;
import org.nustaq.serialization.FSTObjectOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.helpers.MessageFormatter;

import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.util.HashSet;
import java.util.Set;

/**
 * @author will.ma
 * @date 2018-6-8
 */
@SuppressWarnings({"unused", "WeakerAccess"})
public class FileUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(FileUtils.class);

    public static String getFilePath(String path, String name) {
        return path + (path.endsWith("/") || path.endsWith("\\") ? "" : "/") + name;
    }

    public static String getFilePath(String path, String name, String suffix) {
        return path + (path.endsWith("/") || path.endsWith("\\") ? "" : "/") + name + (StringUtils.isNotBlank(suffix) ? "." + suffix : "");
    }

    public static BasicFileAttributes getAttributes(File file) {
        try {
            return Files.getFileAttributeView(Paths.get(file.getAbsolutePath()), BasicFileAttributeView.class).readAttributes();
        } catch (IOException e) {
            LOGGER.warn("Get File Attributes Error.", e);
            return null;
        }
    }

    public static long getFileSize(File file) {
        try {
            BasicFileAttributes attributes = Files.getFileAttributeView(Paths.get(file.getAbsolutePath()), BasicFileAttributeView.class).readAttributes();
            return attributes.size();
        } catch (IOException e) {
            LOGGER.warn("Get File Attributes Error.", e);
            return 0L;
        }
    }

    public static long getLastModifyTime(File file) {
        try {
            BasicFileAttributes attributes = Files.getFileAttributeView(Paths.get(file.getAbsolutePath()), BasicFileAttributeView.class).readAttributes();
            return attributes.lastModifiedTime().toMillis();
        } catch (IOException e) {
            LOGGER.warn("Get File Attributes Error.", e);
            return 0L;
        }
    }

    public static void mkdirs(String path) {
        File pathDir = new File(path);
        if (!pathDir.exists() && !pathDir.mkdirs()) {
            throw new RuntimeException("Cannot Create Export Folder: " + path);
        }
    }

    public static void delete(String path) {
        File file = new File(path);
        if (file.exists() && !file.delete()) {
            throw new RuntimeException("Cannot Delete File: " + file.getAbsolutePath());
        }
    }

    public static void delete(String path, String name, String suffix) {
        File file = new File(getFilePath(path, name, suffix));
        if (file.exists() && !file.delete()) {
            throw new RuntimeException("Cannot Delete File: " + file.getAbsolutePath());
        }
    }

    public static void deleteAll(File file) {
        if (file.isDirectory()) {
            File[] children = file.listFiles();
            if (children != null && children.length > 0) {
                for (File child : children) {
                    deleteAll(child);
                }
            }
        }
        if (!file.delete()) {
            throw new RuntimeException("Cannot Delete File: " + file.getAbsolutePath());
        }
    }

    public static void deleteAll(String path) {
        File file = new File(path);
        if (file.exists()) {
            deleteAll(file);
        }
    }

    public static boolean move(File source, File dest) {
        if (!source.exists() || dest.exists()) {
            return false;
        }
        try (FileInputStream fileInputStream = new FileInputStream(source);
             BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
             FileOutputStream fileOutputStream = new FileOutputStream(dest)) {
            int    len;
            byte[] data = new byte[1024 * 8];
            while ((len = fileInputStream.read(data)) != -1) {
                fileOutputStream.write(data, 0, len);
            }
        } catch (IOException e) {
            LOGGER.warn(MessageFormatter.format("Move File {} -> {} Failure.", source.getAbsolutePath(), dest.getAbsolutePath()).getMessage(), e);
            return true;
        }
        return source.delete();
    }

    public static boolean exist(String filePath) {
        File file = new File(filePath);
        return file.exists();
    }

    public static boolean exist(String path, String name) {
        return new File(getFilePath(path, name)).exists();
    }

    public static boolean exist(String path, String name, String suffix) {
        return new File(getFilePath(path, name, suffix)).exists();
    }

    public static byte[] readBytes(String filePath) {
        File file = new File(filePath);
        if (!file.exists()) {
            throw new RuntimeException("File [" + filePath + "] does not exist!");
        }
        return readBytes(file);
    }

    public static byte[] readBytes(File file) {
        try {
            try (FileInputStream fileInputStream = new FileInputStream(file); DataInputStream in = new DataInputStream(fileInputStream)) {
                byte[] bytes = new byte[new Long(file.length()).intValue()];
                in.readFully(bytes);
                return bytes;
            }
        } catch (IOException e) {
            LOGGER.error("File [" + file.getAbsolutePath() + "] cannot read", e);
            return null;
        }
    }

    public static boolean writeBytes(String filePath, byte[] bytes) {
        File file = getWritableFile(filePath);
        return writeBytes(file, bytes);
    }

    public static boolean writeBytes(File file, byte[] bytes) {
        try (FileOutputStream fileOutputStream = new FileOutputStream(file); DataOutputStream out = new DataOutputStream(fileOutputStream)) {
            out.write(bytes);
            return true;
        } catch (Exception e) {
            LOGGER.error("File [" + file.getAbsolutePath() + "] write error!", e);
            return false;
        }
    }

    public static String readText(String filePath, String charset) {
        File file = new File(filePath);
        if (!file.exists()) {
            throw new RuntimeException("File [" + filePath + "] does not exist!");
        }
        return readText(file, charset);
    }

    public static String readText(File file, String charset) {
        StringBuilder sb = new StringBuilder();
        try (FileInputStream fileInputStream = new FileInputStream(file);
             InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, charset);
             BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
            String str;
            while ((str = bufferedReader.readLine()) != null) {
                sb.append(str).append('\n');
            }
        } catch (IOException e) {
            LOGGER.error("File [" + file.getAbsolutePath() + "] cannot read", e);
        }
        return sb.toString();
    }

    public static boolean writeText(String filePath, String content) {
        File file = getWritableFile(filePath);
        return writeText(file, content);
    }

    public static boolean writeText(File file, String content) {
        try (FileWriter fileWriter = new FileWriter(file)) {
            fileWriter.write(content);
            return true;
        } catch (Exception e) {
            LOGGER.error("File [" + file.getAbsolutePath() + "] write error!", e);
            return false;
        }
    }

    @SuppressWarnings("unchecked")
    private static <T> T streamedFstInput(InputStream inputStream) throws IOException, ClassNotFoundException, ClassCastException {
        try (FSTObjectInput fstInput = new FSTObjectInput(inputStream)) {
            return (T) fstInput.readObject();
        }
    }

    public static <T> T read(String path, String name, String suffix) {
        return read(getFilePath(path, name, suffix));
    }

    public static <T> T read(String path, String name) {
        return read(getFilePath(path, name, null));
    }

    public static <T> T read(String filePath) {
        File file = new File(filePath);
        if (!file.exists()) {
            throw new RuntimeException("File [" + filePath + "] does not exist!");
        }
        return read(file);
    }

    public static <T> T read(File file) {
        try {
            try (FileInputStream fileInputStream = new FileInputStream(file);
                 BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream)) {
                return streamedFstInput(bufferedInputStream);
            }
        } catch (IOException e) {
            LOGGER.error("File [" + file.getAbsolutePath() + "] cannot read", e);
            return null;
        } catch (ClassNotFoundException e) {
            LOGGER.error("File [" + file.getAbsolutePath() + "] cannot find class", e);
            return null;
        } catch (ClassCastException e) {
            LOGGER.error("File [" + file.getAbsolutePath() + "] cannot be cast to type <T>", e);
            return null;
        }
    }

    private static void streamedFstOutput(OutputStream outputStream, Object value) throws IOException {
        try (FSTObjectOutput fstOut = new FSTObjectOutput(outputStream)) {
            fstOut.writeObject(value);
            fstOut.flush();
        }
    }

    public static <T> boolean write(String path, String name, String suffix, T object) {
        mkdirs(path);
        return write(getFilePath(path, name, suffix), object);
    }

    public static <T> boolean write(String path, String name, T object) {
        mkdirs(path);
        return write(getFilePath(path, name, null), object);
    }

    public static <T> boolean write(String filePath, T data) {
        File file = getWritableFile(filePath);
        return write(file, data);
    }

    public static <T> boolean write(File file, T data) {
        try (FileOutputStream fileOutputStream = new FileOutputStream(file);
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream)) {
            streamedFstOutput(bufferedOutputStream, data);
            return true;
        } catch (Exception e) {
            LOGGER.error("File [" + file.getAbsolutePath() + "] write error!", e);
            return false;
        }
    }

    public static File getWritableFile(String filePath) {
        File file = new File(filePath);
        if (!file.exists()) {
            try {
                if (!file.createNewFile()) {
                    throw new RuntimeException("File [" + filePath + "] create failure!");
                }
            } catch (IOException e) {
                throw new RuntimeException("File [" + filePath + "] create failure!", e);
            }
        } else {
            throw new RuntimeException("File [" + filePath + "] already exists!");
        }
        return file;
    }

    public static boolean isWritable(File file) {
        if (!file.exists()) {
            try {
                return file.createNewFile();
            } catch (IOException e) {
                return false;
            }
        } else {
            return false;
        }
    }

    public static <T> boolean writeJson(String path, String name, String suffix, T cache) {
        File file = getWritableFile(getFilePath(path, name, suffix));
        return writeText(file, JSON.toJSONString(cache, SerializerFeature.PrettyFormat));
    }

    public static <T> boolean writeJson(String path, String name, T cache) {
        File file = getWritableFile(getFilePath(path, name, "json"));
        return writeText(file, JSON.toJSONString(cache, SerializerFeature.PrettyFormat));
    }

    public static <T> boolean writeReadable(String path, String name, T cache) {
        return write(path, name, cache) && writeJson(path, name, cache);
    }

    public static void changeUser(File file, String user) {
        changeOwner(file, user, null);
    }

    public static void changeGroup(File file, String group) {
        changeOwner(file, null, group);
    }

    public static void changeOwner(File file, String user, String group) {
        Path                       path          = Paths.get(file.getAbsolutePath());
        UserPrincipalLookupService lookupService = FileSystems.getDefault().getUserPrincipalLookupService();
        PosixFileAttributeView     view          = Files.getFileAttributeView(path, PosixFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);
        if (view == null) {
            throw new RuntimeException("Cannot Change Owner of File: " + file.getAbsolutePath() + ", File System Not Support.");
        }
        try {
            if (StringUtils.isNotBlank(group)) {
                try {
                    GroupPrincipal groupPrincipal = lookupService.lookupPrincipalByGroupName(group);
                    view.setGroup(groupPrincipal);
                } catch (UserPrincipalNotFoundException e) {
                    throw new RuntimeException("Cannot Change Owner of File: " + file.getAbsolutePath() + ", Group " + group + " Not Found.", e);
                }
            }
            if (StringUtils.isNotBlank(user)) {
                try {
                    UserPrincipal userPrincipal = lookupService.lookupPrincipalByName(user);
                    view.setOwner(userPrincipal);
                } catch (UserPrincipalNotFoundException e) {
                    throw new RuntimeException("Cannot Change Owner of File: " + file.getAbsolutePath() + ", User " + user + " Not Found.", e);
                }
            }
        } catch (SecurityException e) {
            throw new RuntimeException("Cannot Change Owner of File: " + file.getAbsolutePath() + ", You may not have the Permission to Access the file.", e);
        } catch (IOException e) {
            throw new RuntimeException("Cannot Change Owner of File: " + file.getAbsolutePath(), e);
        }
    }

    public static void changeMod(File file, int mod) {
        Path path = Paths.get(file.getAbsolutePath());
        try {
            Set<PosixFilePermission> perms     = new HashSet<>();
            int                      ownerMode = mod / 100;
            int                      groupMode = mod / 10 % 10;
            int                      otherMode = mod % 10;
            if ((ownerMode & 4 >> 2) == 1) {
                perms.add(PosixFilePermission.OWNER_READ);
            }
            if ((ownerMode & 2 >> 1) == 1) {
                perms.add(PosixFilePermission.OWNER_WRITE);
            }
            if ((ownerMode & 1) == 1) {
                perms.add(PosixFilePermission.OWNER_EXECUTE);
            }

            if ((groupMode & 4 >> 2) == 1) {
                perms.add(PosixFilePermission.GROUP_READ);
            }
            if ((groupMode & 2 >> 1) == 1) {
                perms.add(PosixFilePermission.GROUP_WRITE);
            }
            if ((groupMode & 1) == 1) {
                perms.add(PosixFilePermission.GROUP_EXECUTE);
            }

            if ((otherMode & 4 >> 2) == 1) {
                perms.add(PosixFilePermission.OTHERS_READ);
            }
            if ((otherMode & 2 >> 1) == 1) {
                perms.add(PosixFilePermission.OTHERS_WRITE);
            }
            if ((otherMode & 1) == 1) {
                perms.add(PosixFilePermission.OTHERS_EXECUTE);
            }

            Files.setPosixFilePermissions(path, perms);
        } catch (SecurityException e) {
            throw new RuntimeException("Cannot Change Mode of File: " + file.getAbsolutePath() + ", You may not have the Permission to Access the file.", e);
        } catch (IOException e) {
            throw new RuntimeException("Cannot Change Mode of File: " + file.getAbsolutePath(), e);
        }
    }

    /**
     * Gets file name.
     *
     * @param filename the filename
     * @return the file name
     */
    public static String getFileName(String filename) {
        if (filename != null && filename.trim().length() > 0) {
            int index = filename.lastIndexOf(File.separatorChar);
            if (index > -1 && index < filename.length() - 1) {
                return filename.substring(index + 1);
            }
            if (File.separatorChar == '/') {
                index = filename.lastIndexOf('\\');
            } else {
                index = filename.lastIndexOf('/');
            }
            if (index > -1 && index < filename.length() - 1) {
                return filename.substring(index + 1);
            }
        }
        return filename;
    }

    /**
     * Gets file suffix.
     *
     * @param filename the filename
     * @return the file suffix
     */
    public static String getFileSuffix(String filename) {
        return getFileSuffix(filename, null);
    }

    /**
     * Gets file suffix.
     *
     * @param filename          the filename
     * @param defaultFileSuffix the default file suffix
     * @return the file suffix
     */
    public static String getFileSuffix(String filename, String defaultFileSuffix) {
        if ((filename != null) && (filename.length() > 0)) {
            int dot = filename.lastIndexOf('.');
            if ((dot > -1) && (dot < (filename.length() - 1))) {
                return filename.substring(dot + 1);
            }
        }
        return defaultFileSuffix;
    }

}
